<?php


namespace App\Common\Services;

use Illuminate\Support\Facades\Redis;

/**
 * Class UserTokenService
 * Describe 登录TOKEN操作类
 * @package App\Common\Services
 */
class UserTokenService
{

    // 头部组成
    private static $header = [
        'type' => 'JWT',
        'alg' => 'sha256'
    ];

    // 秘钥
    private static $secret = 'Z!wj$20*21.';

    /**
     * User: zhangchaoqun
     * DateTime: 2021/6/10 10:45 上午
     * Describe: Base64加密
     * Version: V1.0
     * @param string $input 加密内容
     * @return array|string|string[]
     */
    private static function base64UrlEncode(string $input)
    {
        return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));
    }

    /**
     * User: zhangchaoqun
     * DateTime: 2021/6/10 10:55 上午
     * Describe: 获取TOKEN
     * Version: V1.0
     * [
     *  'ttl' => 60,  // 有效时间（单位：秒），为0则长久保持登录
     *  'source' => 'user' // 来源user：用户 doctor：医生
     *  'id' => 1, // 根据来源可判断是用户ID 还是医生ID
     * ]
     * @param array $payload
     * @return false|string
     * @throws \Exception
     */
    public static function getToken(array $payload)
    {
        if (is_array($payload)) {
            if (!isset($payload['source']) || empty($payload['source'])) {
                throw new \Exception('参数异常，生成TOKEN失败');
            }

            if (!isset($payload['id']) || empty($payload['id'])) {
                throw new \Exception('参数异常，生成TOKEN失败');
            }

            // 检查是否存在同操作人已有TOKEN，有则清除
            $keyArr = Redis::keys($payload['source'] . '_' . $payload['id'] . '_*');

            if ($keyArr) {
                foreach ($keyArr as $item) {
                    $item = str_replace(config('his.redis_prefix'), '', $item);
                    Redis::del($item);
                }
            }

            // 生成TOKEN唯一标识，此Token要存入REDIS保证可以退出可控制
            $payload['jti'] = $payload['source'] . '_' . $payload['id'] . '_' . md5(uniqid('JWT')) . time();
            Redis::set($payload['jti'], json_encode($payload));

            // 设置过期时间
            if ($payload['ttl'] != 0) {
                Redis::expire($payload['jti'], $payload['ttl']);
            }

            $base64header = self::base64UrlEncode(json_encode(self::$header, JSON_UNESCAPED_UNICODE));
            $base64payload = self::base64UrlEncode(json_encode($payload, JSON_UNESCAPED_UNICODE));
            $token = $base64header . '.' . $base64payload . '.' . self::signature($base64header . '.' . $base64payload, self::$secret, self::$header['alg']);
            return $token;
        } else {
            throw new \Exception('参数异常，生成TOKEN失败');
        }
    }

    /**
     * User: zhangchaoqun
     * DateTime: 2021/6/10 11:09 上午
     * Describe: 验证TOKEN合法性
     * Version: V1.0
     * @param string $Token
     * @return false|mixed
     * @throws \Exception
     */
    public static function verifyToken(string $Token)
    {
        $tokens = explode('.', $Token);
        if (count($tokens) != 3) {
            throw new \Exception('TOKEN参数异常');
        }

        list($base64header, $base64payload, $sign) = $tokens;

        //获取jwt算法
        $base64decodeheader = json_decode(self::base64UrlDecode($base64header), JSON_OBJECT_AS_ARRAY);
        if (empty($base64decodeheader['alg'])) {
            throw new \Exception('TOKEN加密算法异常');
        }

        //签名验证
        if (self::signature($base64header . '.' . $base64payload, self::$secret, $base64decodeheader['alg']) !== $sign) {
            throw new \Exception('TOKEN签名校验失败');
        }

        $payload = json_decode(self::base64UrlDecode($base64payload), JSON_OBJECT_AS_ARRAY);

        // 验证TOKEN是否过期
        if (!Redis::exists($payload['jti'])) {
            throw new \Exception('TOKEN已失效请重新登录');
        }

        return $payload;
    }

    /**
     * User: zhangchaoqun
     * DateTime: 2021/6/10 10:47 上午
     * Describe: Base64解密
     * Version: V1.0
     * @param string $input 解密内容
     * @return false|string
     */
    private static function base64UrlDecode(string $input)
    {
        $remainder = strlen($input) % 4;
        if ($remainder) {
            $addlen = 4 - $remainder;
            $input .= str_repeat('=', $addlen);
        }
        return base64_decode(strtr($input, '-_', '+/'));
    }

    /**
     * User: zhangchaoqun
     * DateTime: 2021/6/10 10:40 上午
     * Describe: 生成签名
     * Version: V1.0
     * @param string $input 签名内容
     * @param string $secret 秘钥
     * @param string $alg 算法
     * @return array|string|string[]
     */
    public static function signature(string $input, string $secret, string $alg = 'sha256')
    {
        return self::base64UrlEncode(hash_hmac($alg, $input, $secret));
    }
}
